package pl.gettoknowme.mobile.android.integration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import pl.gettoknowme.mobile.android.model.DBAdapter;
import pl.gettoknowme.mobile.android.model.dbtypes.Integration;
import pl.gettoknowme.mobile.android.model.dbtypes.Integration.IntegrationType;
import pl.gettoknowme.mobile.android.network.NetworkException;
import pl.gettoknowme.mobile.android.network.NetworkManager;
import pl.gettoknowme.mobile.android.network.request.impl.SmsStatsHttpRequestWrapper;
import pl.gettoknowme.mobile.android.network.response.HttpResponseWrapper;
import pl.gettoknowme.mobile.android.services.sms.SmsAnalyzer;
import pl.gettoknowme.mobile.android.services.sms.SmsMessage;
import pl.gettoknowme.mobile.android.services.sms.SmsNumberStats;
import pl.gettoknowme.mobile.android.services.sms.SmsReader;
import pl.gettoknowme.mobile.android.services.sms.SmsSingleWordStats;
import pl.gettoknowme.mobile.android.services.sms.SmsMessage.MessageType;
import pl.gettoknowme.mobile.android.util.LoggingUtil;
import android.content.Context;

/**
 * Implementacja wykonania wątku integracji statystyk SMS-ów
 * @author Michał Kołodziejski
 */
public class SmsStatsIntegrationRunnable implements Runnable {
    
    protected Context context;
    protected String userId;
    protected Long lastIntegrationTimestamp;
    protected long nowTimestamp;
    
    public SmsStatsIntegrationRunnable(Context context, String userId, Long lastIntegrationTimestamp) {
        this.context = context;
        this.userId = userId;
        this.lastIntegrationTimestamp = lastIntegrationTimestamp;
    }

    @Override
    public void run() {
        // wczytanie wiadomości
        List<SmsMessage> smsList = getSmsList();
        nowTimestamp = System.currentTimeMillis();
        
        // analiza wiadomości
        Map<String, Integer> analyzedMap = analyzeSmsList(smsList);
        
        // transformacja do postaci integracji
        Map<String, SmsSingleWordStats> transformedMap =  transformAnalyzedMap(analyzedMap);
        
        // lista statystyk ilości SMS-ów
        List<SmsNumberStats> smsNumberStats = getSmsNumberStats(smsList);
        
        // wysłanie
        DBAdapter dbAdapter = DBAdapter.getInstance(context);
        Integration integration = new Integration();
        integration.setIntegrationTime(nowTimestamp);
        integration.setIntegrationType(IntegrationType.SMS_STATS);
        if(sendAPIRequest(transformedMap, smsNumberStats)){
            LoggingUtil.d(this, "API request finished successfully");
            
            // zakończone powodzeniem - zapisujemy info o udanej integracji
            integration.setSuccessful(true);
            dbAdapter.getIntegrationDAO().insert(integration);
            
        } else {
            // zakończone niepowodzeniem - zapisujemy info o udanej integracji
            integration.setSuccessful(false);
            dbAdapter.getIntegrationDAO().insert(integration);
        }
        
    }

    
    /**
     * Pobiera listę SMS-ów do analizy
     * @return lista SMS-ów do analizy
     */
    private List<SmsMessage> getSmsList(){
        SmsReader smsReader = new SmsReader(context);
        List<SmsMessage> smsList = null;
        
        long t1 = System.currentTimeMillis();
        if(lastIntegrationTimestamp == null){
            smsList = smsReader.getSmsList(null, null);
        } else {
            smsList = smsReader.getSmsList(lastIntegrationTimestamp.longValue(), null, null);
        }
        long t2 = System.currentTimeMillis();
        
        LoggingUtil.d(this, "smsList.size(): " + smsList.size() 
                + ", read time: "+(t2-t1));
        
        return smsList;
    }
    
    
    /**
     * Analizuje przekazaną listę SMS-ów
     * @param smsList lista SMS-ów do analizy
     * @return mapa: słowo -> ilość wystąpień
     */
    private Map<String, Integer> analyzeSmsList(List<SmsMessage> smsList){
        SmsAnalyzer smsAnalyzer = new SmsAnalyzer();
        Map<String, Integer> analyzedMap = new HashMap<String, Integer>();
        long t1 = System.currentTimeMillis();
        int i = 0;
        for(SmsMessage smsMessage : smsList){
            ++i;
            if(i%100 == 0){
                LoggingUtil.d(this, "Analyzing message number = "+i);
            }
            analyzedMap = smsAnalyzer.analyzeSmsMessage(smsMessage, analyzedMap);
        }
        long t2 = System.currentTimeMillis();
        LoggingUtil.d(this, "analyze time: "+(t2-t1));
        LoggingUtil.d(this, "analyze map: "+analyzedMap.toString());
        
        return analyzedMap;
    }
    
    
    /**
     * Przekształca mapę SMS-ów do postaci akceptowanej przez API
     * @param analyzedMap mapa wejściowa
     * @return mapa wyjściowa
     */
    private Map<String, SmsSingleWordStats> transformAnalyzedMap(Map<String, Integer> analyzedMap){
        Map<String, SmsSingleWordStats> resultMap = new HashMap<String, SmsSingleWordStats>();
        
        for(Map.Entry<String, Integer> entry : analyzedMap.entrySet()){
            SmsSingleWordStats stats = new SmsSingleWordStats();
            stats.setToTime(nowTimestamp);
            if(lastIntegrationTimestamp != null){
                stats.setFromTime(lastIntegrationTimestamp.longValue());
            } else {
                stats.setFromTime(null);
            }
            stats.setOccurencesNumber(entry.getValue().intValue());
            
            resultMap.put(entry.getKey(), stats);
        }
        
        return resultMap;
    }
    
    /**
     * Zwraca statystyki ilościowe wysłanych i odebranych SMS-ów z numerami telefonu
     * @param smsList lista SMS-ów do analizy
     * @return statystyki
     */
    private List<SmsNumberStats> getSmsNumberStats(List<SmsMessage> smsList){
        Map<String, SmsNumberStats> smsNumberStatsMap = new HashMap<String, SmsNumberStats>();
        for(SmsMessage smsMessage : smsList){
            SmsNumberStats smsNumberStats = smsNumberStatsMap.get(smsMessage.getAddress());
            
            if(smsNumberStats == null){
                smsNumberStats = new SmsNumberStats();
                smsNumberStats.setNumberHash(smsMessage.getAddress());
                smsNumberStats.setToTime(nowTimestamp);
                if(lastIntegrationTimestamp != null){
                    smsNumberStats.setFromTime(lastIntegrationTimestamp.longValue());
                } else {
                    smsNumberStats.setFromTime(null);
                }
                
                smsNumberStatsMap.put(smsNumberStats.getNumberHash(), smsNumberStats);
            }
            
            if(MessageType.MESSAGE_TYPE_INBOX.equals(smsMessage.getMessageType())){
                smsNumberStats.setIncomingSmsNumber(smsNumberStats.getIncomingSmsNumber() + 1);
                
            } else if(MessageType.MESSAGE_TYPE_SENT.equals(smsMessage.getMessageType())){
                smsNumberStats.setOutgoingSmsNumber(smsNumberStats.getOutgoingSmsNumber() + 1);
            }
        }
        return new ArrayList<SmsNumberStats>(smsNumberStatsMap.values());
    }
    
    /**
     * Wysyła dane przez API i zwraca informację czy integracja zakończyła się sukcesem
     * @param wordStatsMap mapa statystyk występowania słów
     * @param smsNumberStats lista statystyk ilości SMS-ów wymienianych z numerami tel.
     * @return true jeżeli integracja się powiodła, false w p.p.
     */
    private boolean sendAPIRequest(Map<String, SmsSingleWordStats> wordStatsMap,
            List<SmsNumberStats> smsNumberStats){
        SmsStatsHttpRequestWrapper requestWrapper = new SmsStatsHttpRequestWrapper(userId, wordStatsMap, 
                smsNumberStats);
        NetworkManager networkManager = new NetworkManager();
        HttpResponseWrapper responseWrapper = null;
        
        try {
            LoggingUtil.d(this, "sending API request in SMS integration...");
            responseWrapper = networkManager.sendAPIRequest(requestWrapper);
            LoggingUtil.d(this, "API request sent");
            
        } catch (NetworkException e) {
            LoggingUtil.e(this, "NetworkException occured while sending API request", e);
            return false;
        }
        
        return responseWrapper.isSuccessful();
    }
}
